/* purpose @ redis 客户端封装
 * date    @ 2015-11-15
 * author  @ haibin.wang
 */

#include "rediscli.h"
#include "hiredis.h"
#include <stdlib.h>

#define CHECK_REDIS_INIT_FAILED                                                          \
    if (NULL == m_redis) {                                                               \
        char buff[100] = { 0 };                                                          \
        sprintf(buff, "[%s] redis object is empty.", __func__);                          \
        m_err = buff;                                                                    \
        return false;                                                                    \
    }                                                                                    \
    if (m_bReconn) {                                                                     \
        if (!Reconnect()) {                                                              \
            return false;                                                                \
        }                                                                                \
    }

#define CHECK_REDIS_CMD_INTEGER                                                          \
    if (NULL != r) {                                                                     \
        if (r->type == REDIS_REPLY_INTEGER) {                                            \
            bRet = true;                                                                 \
        } else {                                                                         \
            m_err = m_redis->errstr;                                                     \
        }                                                                                \
        freeReplyObject(r);                                                              \
    } else {                                                                             \
        m_bReconn = true;                                                                \
    }

#define CHECK_REDIS_CMD_STRING(strStr)                                                   \
    if (NULL != r) {                                                                     \
        if (r->type == REDIS_REPLY_STRING) {                                             \
            bRet = true;                                                                 \
            strStr = r->str;                                                             \
        } else if (r->type == REDIS_REPLY_NIL) {                                         \
            bRet = true;                                                                 \
            strStr = "";                                                                 \
        } else {                                                                         \
            m_err = m_redis->errstr;                                                     \
        }                                                                                \
        freeReplyObject(r);                                                              \
    } else {                                                                             \
        m_bReconn = true;                                                                \
        m_err = m_redis->errstr;                                                         \
    }

#define CHECK_REDIS_CMD_STATUS                                                           \
    if (NULL != r) {                                                                     \
        if (r->type == REDIS_REPLY_STATUS && strcasecmp(r->str, "OK") == 0) {            \
            bRet = true;                                                                 \
        } else {                                                                         \
            m_err = m_redis->errstr;                                                     \
        }                                                                                \
        freeReplyObject(r);                                                              \
    } else {                                                                             \
        m_bReconn = true;                                                                \
    }

RedisClient::RedisClient()
: m_redis(NULL), m_ip(""), m_port(0), m_timeout(0), m_bReconn(true)
{
}

RedisClient::~RedisClient()
{
    DisConnect();
}

bool RedisClient::Expire(const char *key, int seconds)
{
    bool bRet = false;
    CHECK_REDIS_INIT_FAILED;
    redisReply *r = (redisReply *)redisCommand(m_redis, "expire %s %d", key, seconds);
    CHECK_REDIS_CMD_INTEGER;
    return bRet;
}

bool RedisClient::Del(const char *key)
{
    bool bRet = false;
    CHECK_REDIS_INIT_FAILED;
    redisReply *r = (redisReply *)redisCommand(m_redis, "del %s", key);
    CHECK_REDIS_CMD_INTEGER;
    return bRet;
}

bool RedisClient::Set(const char *key, const char *value, int timeout)
{
    bool bRet = false;
    CHECK_REDIS_INIT_FAILED;
    redisReply *r = NULL;
    if (timeout > 0) {
        r = (redisReply *)redisCommand(m_redis, "SET %s %b EX %d", key, value, strlen(value), timeout);
    } else {
        r = (redisReply *)redisCommand(m_redis, "SET %s %b", key, value, strlen(value));
    }
    CHECK_REDIS_CMD_STATUS;
    return bRet;
}

bool RedisClient::Set(const char *key, int value, int timeout)
{
    char buffer[20] = { 0 };
    sprintf(buffer, "%d", value);
    return Set(key, buffer, timeout);
}

bool RedisClient::Get(const char *key, std::string &value)
{
    bool bRet = false;
    CHECK_REDIS_INIT_FAILED;
    char cmd[1024] = { 0 };
    sprintf(cmd, "get %s", key);
    redisReply *r = (redisReply *)redisCommand(m_redis, cmd);
    CHECK_REDIS_CMD_STRING(value);
    return bRet;
}

bool RedisClient::ZAdd(const char *key, long long score, const char *value)
{
    bool bRet = false;
    CHECK_REDIS_INIT_FAILED;
    redisReply *r =
    (redisReply *)redisCommand(m_redis, "zadd %s %lld %b", key, score, value, strlen(value));
    CHECK_REDIS_CMD_INTEGER;
    return bRet;
}

bool RedisClient::ZFirstBiggerScore(const char *key, long long score, std::string &value)
{
    bool bRet = false;
    CHECK_REDIS_INIT_FAILED;
    char cmd[1024] = { 0 };
    sprintf(cmd, "zrangebyscore %s %lld +inf limit 0 1", key, score);
    redisReply *r = (redisReply *)redisCommand(m_redis, cmd);
    if (NULL != r) {
        if (r->type == REDIS_REPLY_ARRAY) {
            bRet = true;
            for (size_t i = 0; i < r->elements; ++i) {
                redisReply *childReply = r->element[i];
                if (childReply->type == REDIS_REPLY_STRING) {
                    value = childReply->str;
                }
            }
        } else {
            m_err = m_redis->errstr;
        }
    } else {
        m_bReconn = true;
        m_err = m_redis->errstr;
    }
    freeReplyObject(r);
    return bRet;
}

bool RedisClient::ZScore(const char *key, const char *member, std::string &score)
{
    bool bRet = false;
    CHECK_REDIS_INIT_FAILED;
    char cmd[1024] = { 0 };
    sprintf(cmd, "zscore %s %s", key, member);
    redisReply *r = (redisReply *)redisCommand(m_redis, cmd);
    CHECK_REDIS_CMD_STRING(score);
    return bRet;
}

bool RedisClient::ZRemove(const char *key, const char *member)
{
    bool bRet = false;
    CHECK_REDIS_INIT_FAILED;
    redisReply *r = (redisReply *)redisCommand(m_redis, "zrem %s %b", key, member, strlen(member));
    CHECK_REDIS_CMD_INTEGER;
    return bRet;
}

bool RedisClient::Connect(const char *ip, int port, int timeout)
{
    m_ip = ip;
    m_port = port;
    m_timeout = timeout;
    return Connect();
}

bool RedisClient::Reconnect()
{
    bool bRet = false;
    if (REDIS_OK == redisReconnect(m_redis)) {
        bRet = true;
        m_bReconn = false;
    }
    return bRet;
}

bool RedisClient::Connect()
{
    bool bRet = false;
    if (m_timeout > 0) {
        struct timeval tv;
        tv.tv_sec = m_timeout;
        tv.tv_usec = 0;
        m_redis = redisConnectWithTimeout(m_ip.c_str(), m_port, tv);
        if (m_redis) {
            redisSetTimeout(m_redis, tv);
        }
    } else {
        m_redis = redisConnect(m_ip.c_str(), m_port);
    }
    if (m_redis && !m_redis->err) {
        bRet = true;
        m_bReconn = false;
    } else {
        if (NULL == m_redis) {
            m_err = "reedis connected failed!";
        } else {
            m_err = m_redis->errstr;
        }
    }
    return bRet;
}

void RedisClient::DisConnect()
{
    if (m_redis) {
        redisFree(m_redis);
        m_redis = NULL;
    }
}

const char *RedisClient::GetErrorInfo()
{
    return m_err.c_str();
}

std::string RedisClient::GetServerInfo()
{
    char buf[20] = { 0 };
    sprintf(buf, "%d:%d", m_port, m_timeout);
    std::string strTmp = m_ip;
    strTmp.append(":").append(buf);
    return strTmp;
}
